/*=====================*\
# Cubes - the 4x4x4     #
# Multiplex connection  #
# Version 1.0     10.10 #
\*=====================*/

// Map a LED# to actual pin# is split into columns and layers
// Column pins, ordered so it scans left to right, back to front
const int PC[] = { 17, 18, 0, 1,
                   15, 16, 2, 3,
                   12, 11, 6, 5,
                   10,  9, 8, 7 } ; 
// Layers pins, ordered so it scans top to bottom
const int PL[] = { 19, 14, 4, 13 } ;  // (19 is mapped to PB7 - only on non-crystal 8Mhz!)

const int PClen = (sizeof(PC)/sizeof(PC[0])) ;
const int PLlen = (sizeof(PL)/sizeof(PL[0])) ;
//(Arduino note: All lookup tables could be placed in F-memory to save RAM)

// Actual variables
unsigned long Timer ;

#define CSZ 4              // Size of side of cube
byte LED[CSZ*CSZ*CSZ];     // A memory image of the LEDs. (One bit/LED would be enough, here we avoid pack/unpacking)

void setup() {
  // Set pins for output
  for ( int n=0; n<PClen; n++ )         // All column pins
    pinMode(PC[n],OUTPUT);        
  for ( int n=0; n<PLlen; n++ )         // All layer drivers
    if ( PL[n]==19 ) DDRB |= 1 << PB7;  // When seeing "19" map to PB7 pin
    else pinMode(PL[n],OUTPUT);      // else normal pin setup
}

// Here patterns are generated directly in the loop with code
// The loop only runs once for every complete sequence
// Apart from Pattern 1, the others need to constantly "refresh" all layers, using POV
// NB: Altough more than one layer can be turned on, more than one LED overloads the column pin !

void loop() {

  // pattern 1 - turn on each LED singly
  // delay() can be used in this pattern as no other activity needs to run or be monitored
  for ( int z=0; z<PLlen; z++ ) {       // For each layer
    digitalWrite2(PL[z],HIGH);           // HIGH turns transistor on, layer goes LOW, lights ON
    for ( int xy=0; xy<PClen; xy++ ) {  // For each column
      digitalWrite(PC[xy],HIGH) ;       // positive on column - LED lights at intersection
      delay(350);
      digitalWrite(PC[xy],LOW);         // low on column
      delay(150);
    }                                   // Done all columns this layer
    digitalWrite2(PL[z],LOW);            // LOW turns transistor off, layer goes "opencircuit", lights OFF
  }                                     // Done all layers

  // Pattern 2 - turn all LEDs On/Off together, 3 times
  // This is NOT possible, so we switch quickly between layers, relying on POV
  // Can not use delay to time the All-On-period as we need to refresh, hence the "Timer"
  for ( int b=0; b<3 ; b++) {           // 3 times...
    for ( int c=0 ; c<PClen ; c++ ) 
      digitalWrite(PC[c],HIGH) ;        // all columns on
    Timer = millis() ;                  
    while ( millis() - Timer < 350 ) {  // total ON time is 350 (but we do not work out how many blink loops)
      for ( int z=0; z<PLlen; z++ ) {
        digitalWrite2(PL[z],HIGH);      
        delay(5) ;
        digitalWrite2(PL[z],LOW);        
        // (note: no off delay)
      }
    }
    for ( int c=0 ; c<PClen ; c++ )
      digitalWrite(PC[c],LOW) ;         // turn all columns off (redundant as layers are off)
    delay(250) ;                        //  all LEDs are off, so we just wait the OFF period
  }

  // Pattern 3 - Pan across axis.
  // It is NOT possible to show any vertical pattern, 
  //   so we switch quickly between layer-patterns and rely on POV
  // A memory image of the LED pattern is made, and all LEDs are refreshed in all layers (simplifies code)
  
  // We create the pattern in memory image, then ask the refresh routine to display it
  for ( int xyz=0; xyz<3; xyz++) {       // For each axis
    PlaneSet(xyz,0,1);                   // turn on far end
    for ( int SwpN=0; SwpN<3; SwpN++ ) { // For each to-and-fro run, 3 times
      for ( int SwpD=0; SwpD<2; SwpD++ ) { // For direction 0=>1, 1=>-1
        for ( int SwpP=1; SwpP<CSZ; SwpP++ ) { 
          // This loop is for each plane position
          int P = (SwpD==0)?SwpP:CSZ-SwpP-1 ; // the (new) current plane 
          int Pp = P-(SwpD==0?1:-1) ;         // The previous plane
          PlaneSet(xyz,P,1);            // adjust memory image for what is ON
          PlaneSet(xyz,Pp,0) ;          //                                 OFF
          Refresh(72) ;                 // and run the refresh-loop so one axis pass is 500+ms
        }
      }
    }
    PlaneSet(xyz,0,0);                  // off at end
  }
  
  // Pattern 4  - "Replay"
  // A stream of pattern instructions
  // [TBW]
  
}

void PlaneSet(int xyz, int P, int OffOn) {
  // set the "plane" of LEDs in memory image
  for ( int z=(xyz==2?P:0); z<(xyz==2?P+1:CSZ); z++) 
    for ( int y=(xyz==1?P:0); y<(xyz==1?P+1:CSZ); y++) 
      for ( int x=(xyz==0?P:0); x<(xyz==0?P+1:CSZ); x++) 
        LED[(z*CSZ+y)*CSZ+x] = OffOn ;
}

void Refresh(int Delay) {
  // Playback of LED array content; cycle all layers; until Delay time used
  Timer = millis() ;
  while ( millis() - Timer < Delay ) {  // total time refreshing
    for ( int z = 0; z<CSZ; z++ ) {     // for each layer
      digitalWrite2(PL[z],HIGH) ;        // turn layer on
      for ( int y=0; y<CSZ; y++ ) for ( int x=0; x<CSZ; x++ ) 
        digitalWrite(PC[y*4+x],LED[(z*CSZ+y)*CSZ+x]==0?LOW:HIGH) ; // activate or deactivate the column
      delay(5) ;                        // hold layer pattern briefly
      digitalWrite2(PL[z],LOW) ;         // layer off
    }
  }

}

void digitalWrite2(int Pin, byte LH) {
  // digitalWrite that handles "pin 19".
  // (Only needed for turning layer on/off, where pin19 might be used)
  if ( Pin==19 )            // pin19 maps to DB7 pin
    if ( LH==HIGH ) PORTB |= 1 << PB7; else PORTB &= ~(1 << PB7); // set/clear PB7
  else
    digitalWrite(Pin,LH) ;  // normal digital write
}
